One URL means many headaches
Oct 3, 2025 - ⧖ 5 minIt seemed like a simple goal... Be able to access my self-hosted services from anywhere using the same URL regardless of whether I'm on my LAN or using a VPN abroad. With little knowledge of the complexity and nuances of DNS, I dove in headfirst to a topic that should truly be left to the professionals.
For the sake of security through obscurity, we'll assume that the domain I've chosen is mydomain.xyz, and that my local network is set up as 192.168.0.0/16. I have a NixOS server running a Caddy reverse proxy at 192.168.0.10 that has lots of services I want to access!
I first purchased a public domain from Porkbun, the aforementioned mydomain.xyz. With this purchased, I left it as-is - no need to register a public DNS entry for where that domain points to. It won't be anywhere the public can see, so there's no need!
With that domain name registered, I am guaranteed to always have no conflicts with the systems that I am going to explain below. We can start with how I handle things internally within my home LAN.
Internal access
For local DNS, I have a Pi-Hole running in an LXC container on top of Proxmox. This Pi-Hole can not only provide network-wide ad blocking, it can also serve local DNS records. The IP of this is 192.168.0.2
I first created an A record so that apps.mydomain.xyz maps to 192.168.0.10, and then set up a CNAME record to give my chosen application a nicer URL. The CNAME I entered was simple: jellyfin.mydomain.xyz maps to apps.mydomain.xyz. Doing a single A record and then a CNAME record on top with the application name will allow me to update a single A record later if my application server changes IPs.
Finally, I set the Pi-Hole as the name server within my router - an Edgerouter-X. Within the UBNT console, I set both the system Name Server to 192.168.0.2 and also within the Services -> LAN -> Details window in the DNS 1 and DNS 2 fields. This means that all DNS queries should hit the Pi-Hole.
After making these changes, using any client on my LAN can go to jellyfin.mydomain.xyz and it will give them the proper self-hosted service! I'll discuss it further below, but my Android phone gave me a bit of trouble... However, let's talk about external access first.
External access
I use Tailscale for VPN access into my home network. Within Tailscale's web management console, you can go to the DNS tab and add a private nameserver for SplitDNS functionality. For this, I added the IP 100.100.150.103 which is a host on my Tailnet that is running Adguard Home. On that Adguard host, I have TCP ports 53 and 853 open, as well as UDP 53. For the domain in the SplitDNS configuration, I entered mydomain.xyz so that any subdomains are covered.
In my "tailnet DNS", I added an A record so that apps.mydomain.xyz maps to 100.100.150.107 - the Tailnet IP of my application server that is 192.168.0.10 within my LAN. Similarly, I setup a CNAME so that jellyfin.mydomain.xyz maps to apps.mydomain.xyz. This way, I'm using the Tailscale mesh network to securely access my services without using an Exit Node or any other special features. It's just plain DNS resolution over wireguard.
After getting this setup, I can take any client with Tailscale installed and hit jellyfin.mydomain.xyz. It runs with the Tailnet IP's, and there's nothing exposed to the public web! The only snag I hit along the way to this bright future is on my android device.
Android woes
I tried countless times to hit my jellyfin.mydomain.xyz endpoint while on my local network. With Tailscale enabled, it resolved fine because the Tailscale app takes over the network interface's DNS settings when enabled. However, the Android OS level network configuration when not connected to a VPN had one "gotcha" that had me scratching my head for way to long.
On my Samsung S25, you need to go to Settings -> Connections -> More connection settings -> Private DNS. Set this to off, otherwise you'll see all your DNS queries flow through Google's 8.8.8.8 for "privacy" reasons... Yeah sure...
In addition, go to Settings -> Connections -> the cog to the right of your LAN Wi-Fi -> View more. From here, make sure IP settings are "Static", and your DNS 1 and DNS 2 are set to your internal DNS server (in my case, Pi-Hole at 192.168.0.2).
Finally, I had to go into my router and assign a static IP for my phone because now Android won't use DHCP for this Wi-Fi network.
After all this, things are finally all set! Almost...
TLS is still up in the air
I still get a NOT SECURE warning on my services, since I didn't even think about how to TLS to give me that sweet, sweet HTTPS lock in the URL bar. For the short-term so that sites even load, I just setup tls internal within my Caddy configuration for each of my reverse proxy entries.
For example, here's my Caddy service configuration on my NixOS host:
services.caddy = {
enable = true;
virtualHosts."jellyfin.mydomain.xyz".extraConfig = ''
reverse_proxy http://0.0.0.4455
tls internal
'';
};
I know that it will be a difficult climb to figure out how to handle both internal and external requests with TLS, however it will be worth it. In the short term though, I'm just going to enjoy a single URL for a service whether I'm inside or out of my network!
Final thoughts
I was pleasantly surprised how well this works, besides the obvious drawback that I have two places to manage DNS whenever I add a new service. Beyond that, I can say that this will be a great change from using plain IP addresses with ports and an always-on Tailscale Exit node to hit my services. It might not be the final product, however it's a great start in the right direction!